<!doctype html>
<html>
<head>
	<title>Keyboard</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<style>
		:root {
			--white-key-width: 60px;
			--black-key-width: 40px;
			--white-key-height: calc(5 * var(--white-key-width));
			--black-key-height: calc(3 * var(--white-key-width));
			font-family: 'Linux Libertine O', serif;
		}
		* {
			box-sizing: border-box;
		}
		html {
			height: 100%;
			overflow-x: hidden;
		}
		body {
			background: whitesmoke;
			display: flex;
			flex-direction: column;
			gap: 1em;	
			height: 100%;
			justify-content: center;
			margin: 0px;
			padding-bottom: 4em;
		}
		main {
			position: relative;
			height: var(--white-key-height);
			margin-left: auto;
			margin-right: auto;
		}
		.keys {
			display: flex;
			position: absolute;
			top: 0em;
			pointer-events: none;
		}
		.keys.white {
			left: 0em;
			border-radius: 4px 4px 0px 0px;
			border-top: 2px solid #999;
			overflow: hidden;
		}
		.keys.black {
			left: calc(var(--white-key-width) - var(--black-key-width) / 2);
		}

		.key {
			position: relative;
			font-size: 100%;
			pointer-events: all;
			border-radius: 0px 0px 4px 4px;
		}
		.key.white {
			height: var(--white-key-height);
			width: var(--white-key-width);
			border: 2px solid #999;
			border-top: none;
			border-left: none;
			background: white;
			box-shadow: inset 0px 0px 5px 3px rgba(0, 0, 0, 0.15);
		}
		.key.white:first-child {
			border-left: 2px solid #999;
		}
		.key.white:hover, .key.white:focus {
			background: #EEE;
		}
		.key.white:active {
			background: linear-gradient(#EEE, #DDD);
			box-shadow: inset 0px 0px 5px 3px rgba(0, 0, 0, 0.25);
		}
		.key.black {
			height: var(--black-key-height);
			width: var(--black-key-width);
			background: #333;
			color: white;
			border: 2px solid #444;
			box-shadow: inset 0px -4px 4px 5px rgba(255, 255, 255, 0.2);
		}
		.key.black:hover, .key.black:focus {
			background: #444;
		}
		.key.black:active {
			background: linear-gradient(#444, #222);
			box-shadow: inset 0px -4px 4px 5px rgba(255, 255, 255, 0.0);
		}
		.key.black + .key.black {
			margin-left: calc(var(--white-key-width) - var(--black-key-width));
		}
		.key .label {
			position: absolute;
			bottom: 0.5em;
			width: 100%;
			text-align: center;
		}

		.phony {
			visibility: hidden;
			pointer-events: none;
		}

		#target {
			font-size: 300%;
			display: inline-block;
			padding: 0rem 1rem;
			vertical-align: middle;
		}
		#results {
			margin-left: 1em;
			display: inline-block;
			overflow-x: scroll;
			padding: 0rem 1rem;
			vertical-align: middle;
		}

		.answer {
			font-size: 300%;
		}
		.answer.correct {
			color: green;
		}
		.answer.incorrect {
			color: red;
		}
		.top-panel {
			display: flex;
			margin-left: auto;
			margin-right: auto;
		}
		.options {
			display: flex;
			align-items: center;
			justify-content: center;
			gap: 2em;
		}
		.options > div {
			display: flex;
			align-items: center;
			gap: 1ch;
		}

		.octave-2, .octave-3, .octave-4 {
			display: none;
		}
		main, .top-panel { 
			width: calc(var(--white-key-width) * 7);
		}
		@media only screen and (min-width: 850px) {
			.octave-2 { display: block; }
			main, .top-panel { 
				width: calc(var(--white-key-width) * 14);
			}
		}
		@media only screen and (min-width: 1260px) {
			.octave-2, .octave-3 { display: unset; }
			main, .top-panel { 
				width: calc(var(--white-key-width) * 21);
			}
		}
	</style>
</head>
<body>
	<header class="top-panel">
		<span id="target"></span>
		<span id="results"></span>
	</header>
	<main>
		<div class="white keys"></div>
		<div class="black keys"></div>
	</main>
	<footer class="options">
		<div>
			<label for="level">Difficulty</label>
			<input type="range" id="level" min="1" max="95" />
		</div>
		<div>
			<input type="checkbox" id="show-labels" onchange="toggleLabels()"/>
			<label for="show-labels">Show labels</label>
		</div>
	</footer>

	<script>
		const notes = 'CDEFGAB';

		function main() {
			createKeyboard();
			setTarget();
		}

		function setTarget() {
			const difficulty = Number(document.getElementById('level').value);
			const maxNote = Math.floor(difficulty / 5) + 2;
			document.getElementById('target').innerHTML = randomChoice(
				[
					'C', 'F', 'A', 'D', 'B', 'G', 'E', 
					'C♯', 'F♯', 'A♭', 'A♯', 'D♭', 'D♯', 'B♭', 'G♯', 'G♭', 'E♭', 
					'C♭', 'B♯', 'F♭', 'E♯',
				].slice(0, maxNote)
			);
		}

		function createKeyboard() {
			const whiteKeyCount = 28;
			const keyboard  = document.querySelector('main');
			const whiteKeys = document.querySelector('main > .white.keys');
			const blackKeys = document.querySelector('main > .black.keys');
			let octave = 0;
			for (let i = 0; i < whiteKeyCount; i++){
				if (i % 7 == 0) {
					octave += 1;
				}
				const note = notes[i % 7];

				const key = document.createElement('button');
				key.className = `key white ${note} octave-${octave}`;
				key.setAttribute('tabindex', 100 + i * 2);

				const label = document.createElement('div');
				label.className = 'label';
				label.innerHTML = (
					note == 'C' ? 'B♯<br>' : 
					note == 'B' ? 'C♭<br>' : 
					note == 'F' ? 'E♯<br>' :
					note == 'E' ? 'F♭<br>' : ''
				) + note;

				key.append(label);
				whiteKeys.appendChild(key);
			}
			octave = 0;
			for (let i = 0; i < whiteKeyCount - 1; i++){
				if (i % 7 == 0) {
					octave += 1;
				}
				const key = document.createElement('button');
				key.className = `key black`;
				key.setAttribute('tabindex', 100 + i * 2 + 1);

				if (i % 7 == 2 || i % 7 == 6) {
					key.className += ' phony';
				} else {
					key.className += ` ${notes[i % 7]}-sharp ${notes[(i + 1) % 7]}-flat octave-${octave}`;

					const label = document.createElement('div');
					label.className = 'label';
					label.innerHTML = `${notes[i % 7]}♯ ${notes[(i + 1) % 7]}♭`;
					key.appendChild(label);
				}
				blackKeys.appendChild(key);
			}
			for (const key of document.querySelectorAll('.key')) {
				key.addEventListener('click', (evt) => {
					const note = document.getElementById('target').innerHTML;
					const noteClass = note.replace('♭', '-flat').replace('♯', '-sharp');
					const correct = key.classList.contains(noteClass);
					if (!document.getElementById('show-labels').checked) {
						document.getElementById('level').value = Number(
							document.getElementById('level').value
						) + (correct ? 1 : -1);
					}
					document.getElementById('results').innerHTML = 
						(correct 
							? `<span class="answer correct">${note}</span>`
							: `<span class="answer incorrect">${note}</span>`
						) + document.getElementById('results').innerHTML
					setTarget();
				});
			}
			toggleLabels();
		}

		function toggleLabels() {
			const checked = document.getElementById('show-labels').checked;
			for (const label of document.querySelectorAll('.label')) {
				label.style.visibility =  checked ? 'visible' : 'hidden';
			}
		}

		var randomChoice = (arr) => arr[Math.floor(Math.random() * arr.length)];

		main();
	</script>
</body>
</html>
